import xml.parsers.expat

class XmlAttribute:
    def __init__(self, name, value):
        self._name = name
        self._value = value
    
    def __str__(self):
        return self._name + '<' + str(self._value) + '>'  
    
    def Combine(self, other):
        self._value = other._value
        
    def Compare(self, other):
        return []
    
    def Type(self):
        return "Attribute"
    
    def GetValue(self):
        return self._value
    
class DictionaryIter:
    def __init__(self, dict):
        self._idx = -1
        self._dict = dict._content
        self._keys = dict._content.keys()
        self._keys.sort()
    def next(self):
        self._idx += 1
        if self._idx == len(self._keys):
            raise StopIteration()
        return self._dict[self._keys[self._idx]]
    def __iter__(self):
        return self
    
class Dictionary:
    def __init__(self):
        self._content = dict()
        
    def Add(self, element):
        if element._name in self._content.keys():
            self._content[element._name].Combine(element)
        else:
            self._content[element._name] = element
        
    def __getitem__(self, key):
        return self._content[key]

    def __iter__(self):
        return DictionaryIter(self)

    def LastElement(self):
        keys = self._content.keys()
        keys.sort()
        last = keys[len(keys)-1]
        return self._content[last]
    
    def Compare(self, other):
        msg_list = []
        s = set(self._content.keys())
        o = set(other._content.keys())
        common = s & o
        s_minus_o = s - o
        o_minus_s = o - s
        for k in common:
            tmp_msg_list = self._content[k].Compare(other._content[k])
            for msg in tmp_msg_list:
                msg_list.append(msg)
        for k in s_minus_o:
            msg_list.append(self._content[k].Type() + " \'" + k + "\' is lacking")
        for k in o_minus_s:
            msg_list.append(other._content[k].Type() + " \'" + k + "\' should not be present")
        return msg_list

class XmlElement:
    def __init__(self, name, attrs):
        self._name = name
        self._attrs = Dictionary()
        self._data = None
        self._subelements =  Dictionary()
        for attr in attrs:
            a = XmlAttribute(attr, attrs[attr])
            self._attrs.Add(a)
    
    def Combine(self, other):
        self._data = other._data
        for attr in other._attrs:
            self._attrs.Add(attr)
       
    def AddSubelement(self, element):
        self._subelements.Add(element)
    
    def SetData(self,data):
        self._data = data
        
    def __str__(self):
        ret = self._name
        ret += "<"
        if self._data != None:
            ret += str(self._data) 
        ret += ">["
        for attr in self._attrs:
            ret += str(attr)
            if attr != self._attrs.LastElement(): 
                ret += "," 
        ret += "]{"
        for elem in self._subelements:
            ret += str(elem)
            if elem != self._subelements.LastElement(): 
                ret += ","
        ret += "}"
        return ret
    
    def Compare(self, other):
        msg_list = []
        tmp_msg_list =  self._attrs.Compare(other._attrs)
        for msg in tmp_msg_list:
            msg_list.append("<"+self._name+"> => " + msg)
        
        tmp_msg_list =  self._subelements.Compare(other._subelements)
        for msg in tmp_msg_list:
            msg_list.append( "<"+self._name+"> => " + msg)
        return msg_list
        
    def Type(self):
        return "Element"
    
    def __call__(self, attr_name):
        return self._attrs[attr_name]
    
    def __getitem__(self, elem_name):
        return self._subelements[elem_name]
    
    def GetData(self):
        return self._data

class XmlParser:
    def __init__(self):
        self._parser = xml.parsers.expat.ParserCreate()
        self._parser.StartElementHandler = self._StartElement
        self._parser.EndElementHandler = self._EndElement
        self._parser.CharacterDataHandler = self._CharData
        self._element_stack = []
        self._root_element = None
        self.error = None

    def _StackTopElement(self):
        return self._element_stack[len(self._element_stack)-1]

    def _StartElement(self, name, attrs):
        e = XmlElement(name, attrs)
        self._element_stack.append(e)
        
    def _EndElement(self, name):
        e = self._element_stack.pop()
        self._StackTopElement().AddSubelement(e)
        
    def _CharData(self, data):
        self._StackTopElement().SetData(data)
        
    def Parse(self, text):
        self.__init__()
        self._element_stack.append(XmlElement("__root__", []))
        self._root_element = self._StackTopElement()
        try:
            self._parser.Parse(text)
        except xml.parsers.expat.ExpatError as err:
            self.error = str(err)
            self._root_element = None
        except:
            self.error = str("Unknown error")
            self._root_element = None
        return self._root_element
            
    def RootElement(self):
        return self._root_element

class ParamsReader:
    def __init__(self, schema):
        p = XmlParser()
        p.Parse(schema)
        self._schema = p.RootElement()
        
    def ReadParams(self, text):
        p = XmlParser()
        p.Parse(text)
        root = p.RootElement()
        if root != None:
            msg_list = self._schema.Compare(root)
            if len(msg_list) == 0:
                return self._AssignParams(root)
            else:
                for msg in msg_list:
                    print msg
                return False
        else:
            print "XML template is invalid: " + str(p.error)
            return False
        return True
    
    def _AssignParams(self, root):
        raise NotImplementedError()        

class ClothParamsReader(ParamsReader):
    def __init__(self):
        ParamsReader.__init__(self, "<simulation> \
                                        <stabilization> \
                                            <iterations/> \
                                            <error/> \
                                        </stabilization> \
                                        <gravitation/> \
                                        <air_friction/> \
                                        <frame> \
                                            <width/> \
                                            <height/> \
                                            <count/> \
                                        </frame> \
                                        <cloth> \
                                            <size> \
                                                <width/> \
                                                <height/> \
                                            </size> \
                                            <stiffness> \
                                                <param1/> \
                                                <param2/> \
                                            </stiffness> \
                                            <particle_distance/> \
                                        </cloth> \
                                    </simulation>")
        
        self.stabilization_iterations = None
        self.stabilization_error = None
        self.gravitation = None
        self.air_friction = None
        self.frame_width = None
        self.frame_height = None
        self.frame_count = None
        self.cloth_stiffness_param1 = None
        self.cloth_stiffness_param2 = None
        self.cloth_width = None
        self.cloth_height = None
        self.cloth_particle_distance = None
        
    
    def _AssignParams(self, root):
        ret = True
        
        stabilization_iterations = root["simulation"]["stabilization"]["iterations"].GetData()
        if stabilization_iterations == None:
            print "\'stabilization_iterations\' not specified"
            ret = False
        else:
            try:
                stabilization_iterations = int(stabilization_iterations)
                if stabilization_iterations < 1:
                    print "\'stabilization_iterations\' must be greater than zero"
                    ret = False
                else:
                    self.stabilization_iterations = stabilization_iterations
            except:
                print "\'stabilization_iterations\' must be integer number"
                ret = False
        
        stabilization_error = root["simulation"]["stabilization"]["error"].GetData()
        if stabilization_error == None:
            print "\'stabilization_error\' not specified"
            ret = False
        else:
            try:
                stabilization_error = float(stabilization_error)
                if stabilization_error < 0:
                    print "\'stabilization_error\' must not be less than zero"
                    ret = False
                else:
                    self.stabilization_error = stabilization_error
            except:
                print "\'stabilization_error\' must be floating point number"
                ret = False
        
        gravitation = root["simulation"]["gravitation"].GetData()
        if gravitation == None:
            print "\'gravitation\' not specified"
            ret = False
        else:
            try:
                gravitation = float(gravitation)
                if gravitation < 0:
                    print "\'gravitation\' must not be less than zero"
                    ret = False
                else:					
                    self.gravitation = gravitation
            except:
                print "\'gravitation\' must be floating point number"
                ret = False
        
        
        air_friction = root["simulation"]["air_friction"].GetData()
        if air_friction == None:
            print "\'air_friction\' not specified"
            ret = False
        else:
            try:
                air_friction = float(air_friction)
                if air_friction < 0 or air_friction > 1:
                    print "\'air_friction\' must be in range from 0 to 1"
                    ret = False
                else:					
                    self.air_friction = air_friction
            except:
                print "\'air_friction\' must be floating point number"
                ret = False
        
        frame_width = root["simulation"]["frame"]["width"].GetData()
        if frame_width == None:
            print "\'frame_width\' not specified"
            ret = False
        else:
            try:
                frame_width = int(frame_width)
                if frame_width < 1:
                    print "\'frame_width\' must be greater than zero"
                    ret = False
                else:	
                    self.frame_width = frame_width
            except:
                print "\'frame_width\' must be integer number"
                ret = False
        
        frame_height = root["simulation"]["frame"]["height"].GetData()
        if frame_height == None:
            print "\'frame_height\' not specified"
            ret = False
        else:
            try:
                frame_height = int(frame_height)
                if frame_height < 1:
                    print "\'frame_height\' must be greater than zero"
                    ret = False
                else:
                    self.frame_height = frame_height
            except:
                print "\'frame_height\' must be integer number"
                ret = False
        
        frame_count = root["simulation"]["frame"]["count"].GetData()
        if frame_count == None:
            print "\'frame_count\' not specified"
            ret = False
        else:
            try:
                frame_count = int(frame_count)
                if frame_count < 1:
                    print "\'frame_count\' must be greater than zero"
                    ret = False
                else:
                    self.frame_count = frame_count
            except:
                print "\'frame_count\' must be integer number"
                ret = False       
        
        cloth_stiffness_param1 = root["simulation"]["cloth"]["stiffness"]["param1"].GetData()
        if cloth_stiffness_param1 == None:
            print "\'cloth_stiffness_param1\' not specified"
            ret = False
        else:
            try:
                cloth_stiffness_param1 = float(cloth_stiffness_param1)
                if cloth_stiffness_param1 < 0:
                    print "\'cloth_stiffness_param1\' must not be less than zero"
                    ret = False
                else:
                    self.cloth_stiffness_param1 = cloth_stiffness_param1
            except:
                print "\'cloth_stiffness_param1\' must be floating point number"
                ret = False   
        
        cloth_stiffness_param2 = root["simulation"]["cloth"]["stiffness"]["param2"].GetData()
        if cloth_stiffness_param2 == None:
            print "\'cloth_stiffness_param2\' not specified"
            ret = False
        else:
            try:
                cloth_stiffness_param2 = float(cloth_stiffness_param2)
                if cloth_stiffness_param2 < 0:
                    print "\'cloth_stiffness_param2\' must not be less than zero"
                    ret = False
                else:
                    self.cloth_stiffness_param2 = cloth_stiffness_param2
            except:
                print "\'cloth_stiffness_param2\' must be floating point number"
                ret = False   
        
        cloth_particle_distance = root["simulation"]["cloth"]["particle_distance"].GetData()
        if cloth_particle_distance == None:
            print "\'cloth_particle_distance\' not specified"
            ret = False
        else:
            try:
                cloth_particle_distance = float(cloth_particle_distance)
                if cloth_particle_distance < 0:
                    print "\'cloth_particle_distance\' must not be less than zero"
                    ret = False
                else:
                    self.cloth_particle_distance = cloth_particle_distance
            except:
                print "\'cloth_particle_distance\' must be floating point number"
                ret = False   
        
        cloth_width = root["simulation"]["cloth"]["size"]["width"].GetData()
        if cloth_width == None:
            print "\'cloth_width\' not specified"
            ret = False
        else:
            try:
                cloth_width = int(cloth_width)
                if cloth_width < 1:
                    print "\'cloth_width\' must be greater than zero"
                    ret = False
                else:
                    self.cloth_width = cloth_width
            except:
                print "\'cloth_width\' must be integer number"
                ret = False  
        
        cloth_height = root["simulation"]["cloth"]["size"]["height"].GetData()
        if cloth_height == None:
            print "\'cloth_height\' not specified"
            ret = False
        else:
            try:
                cloth_height = int(cloth_height)
                if cloth_height < 1:
                    print "\'cloth_height\' must be greater than zero"
                    ret = False
                else:
                    self.cloth_height = cloth_height
            except:
                print "\'cloth_height\' must be integer number"
                ret = False  
        return ret   

class ClothParamsFileReader(ClothParamsReader):
    def __init__(self):
        ClothParamsReader.__init__(self)
        
    def ReadParams(self, file_name):
        file = open(file_name, 'r')
        text = file.read()
        result = ClothParamsReader.ReadParams(self, text)
        file.close()
        return result
        

class Command:
    def __init__(self, object, operation):
        self.object = object
        self.operation = operation
        
    def __str__(self):
        return "/" + str(self.object) + ":" + str(self.operation) + "/"

class CommandsReaderException():
    def __init__(self, msg):
        self.msg = msg
    def __str__(self):
        return self.msg
 
class StopParse():
    pass   

class CommandsReader:
    parser = XmlParser()
    move_schema = parser.Parse("""<move x="0" y="0" z="0"/>""")
    rotate_schema = parser.Parse("""<rotate x="0" y="0" z="0" alpha="0"/>""")
    scale_schema = parser.Parse("""<scale s="0"/>""")
    delete_schema = parser.Parse("""<delete/>""")
    create_schema = parser.Parse("""<create type="0" size="0" x="0" y="0" z="0"/>""")
    wait_schema = parser.Parse("""<wait t="0"/>""")
    object_schema = parser.Parse("""<object id="0"/>""")
    camera_schema = parser.Parse("""<camera/>""")
    
    def Commands(self):
        return self._commands
    
    def ReadCommands(self, text):
        try:
            self._parser.Parse(text)
        except CommandsReaderException as ex:
            print ex
            return False
        except StopParse:
            pass
            return True
    
    def __init__(self):
        self._commands = []
        self._parser = xml.parsers.expat.ParserCreate()
        self._parser.StartElementHandler = self._BlockElementStart
        self._parser.EndElementHandler = self._BlockElementEnd
        self._parser.CharacterDataHandler = self._BlockCharData
    
    def _BlockElementStart(self, name, attrs):    
        if name != "commands":
            raise  CommandsReaderException("No \'commands\' block")
        else:
            self._parser.StartElementHandler = self._ObjectElementStart
            self._parser.EndElementHandler = self._ObjectElementEnd
            self._parser.CharacterDataHandler = self._ObjectCharData
         
    def _BlockElementEnd(self, name):
        pass
    
    def _BlockCharData(self, data):
        pass
            
    def _CommandElementStart(self, name, attrs):
        e = XmlElement(name, attrs)
        if name == "move":
            if self._CheckSchema(CommandsReader.move_schema["move"], e):
                self._commands.append(Command(self._object, e)) 
            else:
                raise CommandsReaderException("\'" + name + "\' improperly used")
        elif name == "rotate":
            if self._CheckSchema(CommandsReader.rotate_schema["rotate"], e):
                self._commands.append(Command(self._object, e)) 
            else:
                raise CommandsReaderException("\'" + name + "\' improperly used")
        elif name == "scale":
            if self._CheckSchema(CommandsReader.scale_schema["scale"], e):
                self._commands.append(Command(self._object, e)) 
            else:
                raise CommandsReaderException("\'" + name + "\' improperly used")
        elif name == "delete":
            if self._CheckSchema(CommandsReader.delete_schema["delete"], e):
                self._commands.append(Command(self._object, e)) 
            else:
                raise CommandsReaderException("\'" + name + "\' improperly used")
        elif name == "create":
            if self._CheckSchema(CommandsReader.create_schema["create"], e):
                self._commands.append(Command(self._object, e)) 
            else:
                raise CommandsReaderException("\'" + name + "\' improperly used")
        else:
            raise CommandsReaderException("\'" + name + "\' not recognized")
    
    def _CommandElementEnd(self, name):
        if name == "object" or name == "camera":
            self._object = None
            self._parser.StartElementHandler = self._ObjectElementStart
            self._parser.EndElementHandler = self._ObjectElementEnd
            self._parser.CharacterDataHandler = self._ObjectCharData
    
    def _CommandCharData(self, data):
        pass
    
    def _CheckSchema(self, schema, element):
        msg_list = schema.Compare(element)
        if len(msg_list) == 0:
            return True
        return False
        
    def _ObjectElementStart(self, name, attrs):
        e = XmlElement(name, attrs)
        if name == "wait":
            if self._CheckSchema(CommandsReader.wait_schema["wait"], e):
                self._commands.append(Command(None, e))
            else:
                raise CommandsReaderException("\'" + name + "\' improperly used")
        elif name == "camera":
            if self._CheckSchema(CommandsReader.camera_schema["camera"], e):
                self._object = e
                self._parser.StartElementHandler = self._CommandElementStart
                self._parser.EndElementHandler = self._CommandElementEnd
                self._parser.CharacterDataHandler = self._CommandCharData
            else:
                raise CommandsReaderException("\'" + name + "\' improperly used")
        elif name == "object":
            if self._CheckSchema(CommandsReader.object_schema["object"], e):
                self._object = e
                self._parser.StartElementHandler = self._CommandElementStart
                self._parser.EndElementHandler = self._CommandElementEnd
                self._parser.CharacterDataHandler = self._CommandCharData
            else:
                raise CommandsReaderException("\'" + name + "\' improperly used")
        else:
            raise CommandsReaderException("\'" + name + "\' not recognized")
    
    def _ObjectElementEnd(self, name):
        if name == "commands":
            raise StopParse()
    
    def _ObjectCharData(self, data):
        pass
    
class CommandsFileReader(CommandsReader):
    def ReadCommands(self, file_name):
        file = open(file_name, 'r')
        text = file.read()
        result = CommandsReader.ReadCommands(self, text)
        file.close()
        return result

    
if __name__ == "__main__":
    p = XmlParser()
    #p.Parse("""<?xml version="1.0"?><a gghg="4" tral="gfgf"><b>aaaa</b><c/></a>""")
    p.Parse("""<wait t="34"/>""")
    print p.RootElement()
    
    cpr = ClothParamsReader()
    print cpr._schema
    print cpr.ReadParams("""<cloth><k1>3.4</k1><k2/><k3/><particle_distance/><size><width/><height/></size></cloth>""")
    
    cr = CommandsReader()
    print cr.ReadCommands("""<commands><wait t="34"/><object id="0"><move/></object></commands>""")
    for c in cr.Commands():
        print c
